Basic Game Tutorial 6: Items
Congratulations on making it to the final part of this project! Hopefully you've learned a lot throughout these tutorials and feel better about going about creating your own game.
In this part of the tutorial we'll be adding items to collect. As with the previous parts, the way we will achieve this in our program will allow you to add more items very easily.
Let's keep it classic with the good old coin. Our project will begin with just coins for our items, but adding different types will be very simple.
We'll need to begin with a few variables as usual. Just like with the state machine in the previous project, the items need a type and a state. Add the following lines to your program:
50. coin = 0
51.
52. active = 0
53. collect = 1
54. inactive = 2
There we go! We've got a variable called coin
which stores a 0. This will be used as an index into an array of tiles.
Similarly, we have a number of state variables below this which we will use to determine what happens to the coin during the game.
Now we need to create the array of items. Each item needs its own structure with a number of properties:
56. items = [
57. [ .type = coin, .x = 7, .y = 1, .state = active ],
58. [ .type = coin, .x = 8, .y = 0, .state = active ],
59. [ .type = coin, .x = 9, .y = 0, .state = active ],
60. [ .type = coin, .x = 10, .y = 1, .state = active ]
61. ]
As you can see, each item has 4 properties. We have a .type
which stores the type of the item. We have a .x
and .y
which are the level coordinates of the item (different than the screen coordinates, these level coordinates tell us which row and column of the level
array the item will appear in) and finally a .state
property to store the state.
Next up we'll need the tilesheet information to animate the items, just like we needed for the player:
63. itemAnim = [
64. [ .start = 154, .length = 1 ]
65. ]
Since we are only using coins at the moment, we don't need any more information in this array. If we were to add another type of item, we would need more information.
This information can be accessed with itemAnim[0].start
, or, since we have a coin
variable which stores a 0, we can say itemAnim[items[0].type]
. This sort of array indexing, despite looking quite complex, is very useful and worth getting your head around!
We are using the item.type
property as an index into the itemAnim
array.
Lastly, we should create a variable to keep track of the number of coins the player has collected:
67. playerCoins = 0
Excellent. Now we have everything we need to put the items on screen. Head into the main loop for this next part, just after the for loop which draws the level.
We'll be using a for loop to loop over the array of items and draw each one. This for loop will end up being rather long and complex looking, so let's build it step by step. First we just want to actually draw the coins on screen:
102. for i = 0 to len( items ) loop
103. x = items[i].x * tSize
104. y = ( items[i].y + levelOffset ) * tSize
105. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
106. repeat
Run the program and we should see the coins on screen. Of course, without the code to make it happen, we cannot pick the coins up yet.
Before we do that, let's make sure we understand what's happening. Our for loop counts using an i
variable from 0 to the length of our items array. Our items array has 4 elements, so i
will count from 0 to 3.
We create some local x
and y
variables to store the position of the item. This is just to make our code easier to read.
We take the .x
and .y
properties of the current item in question and multiply ithem by the tSize
variable to give us the screen coordinates for the item. With the y position, we must add the levelOffset
in order to put them on the correct row.
Then, on line 105, we use the drawSheet()
function to draw the item. The tricky part here is the tile index:
itemAnim[items[i].type].start
This is actually one property of a structure within an array of structures indexing another array of structures to give us the correct property with which to index into a tilesheet. Try saying that three times quickly.
Since the only item type we are using is a coin
, items[i].type
is always a 0. If we use a 0 as an index into the itemAnim
array, we get the animation tile for the coin.
As mentioned before, this might seem a little pointless since we could simply use the tile number for the coin in the tilesheet, but then when it comes to adding items we'll have a very difficult time indeed.
Collecting the Coins
If we want to be able to collect the coins, we'll have to make this for loop of ours a little more complicated.
First we'll wrap the calculations and the drawsheet()
line in an if statement. We only want to do these things if the item is not inactive
.
102. for i = 0 to len( items ) loop
103. if items[i].state != inactive then
104. x = items[i].x * tSize
105. y = ( items[i].y + levelOffset ) * tSize
106. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
107. endif
108. repeat
Great! Now we need to add an if statement to check if the player has moved into the range of an item.
Collision If Statement
This if statement is going to be quite long indeed. When writing game code there really is no avoiding this sometimes. Ready?
102. for i = 0 to len( items ) loop
103. if items[i].state != inactive then
104. x = items[i].x * tSize
105. y = ( items[i].y + levelOffset ) * tSize
106. if playerX + pSize.x > x and playerX < x + tSize and
107. playerY + pSize.y > y and playerY < y + tSize and
108. items[i].state == active
109. then
110. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
111. endif
112. repeat
Phew, check that out for an if statement! It's not even finished yet, this is just the condition! It's so large that we've split it up across multiple lines to make it easier to read. Remember, you can format your code however you like! There's nothing stopping you from breaking up long lines into multiple to make things clearer.
We are checking if the right hand side of the player ( playerX + pSize.x
) is greater than the left side of the coin (> x
), and
the the left side of the player playerX
is less than the right hand edge of the coin < x + tSize
.
We are also checking if the player's feet playerY + pSize.y
is greater than the top of the coin > y
, and
that the top of the player's head (playerY}) is less than the bottom of the coin tile ({< y + tSize
).
We are also checking that the coin itself has to be active.
These 5 conditions must all be true for this if statement to begin. Now let's actually make something happen in it!
102. for i = 0 to len( items ) loop
103. if items[i].state != inactive then
104. x = items[i].x * tSize
105. y = ( items[i].y + levelOffset ) * tSize
106. if playerX + pSize.x > x and playerX < x + tSize and
107. playerY + pSize.y > y and playerY < y + tSize and
108. items[i].state == active
109. then
110. playNote( 0, 3, 1046.50, 1, 20, 0.5 )
111. playNote( 1, 3, 1396.71, 1, 10, 0.5 )
112. playerCoins += 1
113. items[i].state = collect
114. endif
115. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
116. endif
117. repeat
There we have it. As you can see, the new lines are from 109 to 114. After the then
, we first use two playNote()
functions to play a nice coin collection sound.
We also increase the playerCoins
variable by 1 and change the .state
property of the item to collect
.
By having a state other than active
and inactive
, we can now apply some cool things to happen before the coin vanishes.
When we pick up an item in a game we sometimes see that item shoot into the air a little before vanishing. Let's make this happen by using the .collect
state:
102. for i = 0 to len( items ) loop
103. if items[i].state != inactive then
104. x = items[i].x * tSize
105. y = ( items[i].y + levelOffset ) * tSize
106. if playerX + pSize.x > x and playerX < x + tSize and
107. playerY + pSize.y > y and playerY < y + tSize and
108. items[i].state == active
109. then
110. playNote( 0, 3, 1046.50, 1, 20, 0.5 )
111. playNote( 1, 3, 1396.71, 1, 10, 0.5 )
112. playerCoins += 1
113. items[i].state = collect
114. endif
115. if items[i].state == collect then
116. items[i].y -= 0.15
117. if items[i].y < -1 then
118. items[i].state = inactive
119. endif
120. endif
121. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
122. endif
123. repeat
There we go. That's our for loop all done!
Because of the collect
state, we can make something happen to the item before it vanishes. On line 115 we check if the state of an item is collect
. If it is, we reduce the y position of the item by a small amount. We then have another if statement within this to check if the y position has gone past a certain number. If it has, we change the state to inactive
! Once the state is inactive, the item is no longer drawn due to the if statement on line 103.
Told you it would be a rather large for loop!
We're still missing something... We currently have no way of telling how many coins we have! We need a couple of draw commands to display the number of coins. Let's put these just after our items for loop:
125. drawSheet( tilesheet, 154, 10, 10, scale )
126. drawText( 10 + tSize * 0.75 + 10, 10, tSize * 0.75, grey, playerCoins )
The drawSheet()
line just above puts an image of the coin in the top left corner of our screen. The drawText()
line simply displays the playerCoins
variable next to it!
Run the program and pick up a coin! We should hear a little sound, see the coin pop into the air and our coin counter in the top left should increase. If that's all happening, excellent!
The Program So Far
Alright that's all for now. As usual, below is a copy of the whole program. Make sure we're matching and your program works as intended before moving on to the next tutorial, in which we'll be adding an enemy to the game!
1. background = loadImage( "Kenney/backgrounds", false )
2. tilesheet = loadImage( "Kenney/superPlatformPack", false )
3. chrSheet = loadImage( "Kenney/characters", false )
4.
5. playerX = 0
6. playerY = 0
7.
8. moveSpeed = 5
9.
10. idle = 0
11. walk = 1
12. jump = 2
13. hit = 3
14.
15. state = idle
16.
17. anim = [
18. [ .start = 96, .length = 1 ],
19. [ .start = 97, .length = 11 ],
20. [ .start = 95, .length = 1 ],
21. [ .start = 94, .length = 1 ]
22. ]
23.
24. animationFrame = 0
25.
26. gravity = 1
27. velocity = 0
28.
29. jumpTimer = 0
30. oldA = 0
31.
32. screenX = 0
33. screenY = 0
34.
35. tiles = [ 121, 138, 128, 129, 130 ]
36.
37. level = [
38. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
39. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
40. [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
41. [ 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
42. [ 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
43. [ 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
44. ]
45.
46. levelHeight = 12
47. levelOffset = levelHeight - len( level )
48. tSize = 0
49.
50. coin = 0
51.
52. active = 0
53. collect = 1
54. inactive = 2
55.
56. items = [
57. [ .type = coin, .x = 7, .y = 1, .state = active ],
58. [ .type = coin, .x = 8, .y = 0, .state = active ],
59. [ .type = coin, .x = 9, .y = 0, .state = active ],
60. [ .type = coin, .x = 10, .y = 1, .state = active ]
61. ]
62.
63. itemAnim = [
64. [ .start = 154, .length = 1 ]
65. ]
66.
67. playerCoins = 0
68.
69. loop
70. clear()
71.
72. c = controls( 0 )
73.
74. screenW = gwidth()
75. screenH = gheight()
76. scale = screenH / ( tileSize( tilesheet, 121 ).y * levelHeight )
77. tSize = scale * tileSize( tilesheet, 121 ).y
59. pSize = tileSize( chrSheet, 96 ) * scale
60.
61. if playerX - screenX < screenW * 0.4 then
62. screenX -= moveSpeed
63. endif
64. if playerX - screenX > screenW * 0.6 then
65. screenX += moveSpeed
66. endif
67. if screenX < 0 then
68. screenX = 0
69. endif
70.
71. drawImage( background, -screenX / 2, -screenY, screenH / imageSize( background ).y )
72.
73. for row = 0 to len( level ) loop
74. for col = 0 to len( level[0] ) loop
75. if level[row][col] >= 0 then
76. x = col * tSize
77. y = ( row + levelOffset ) * tSize
78. drawSheet( tilesheet, tiles[level[row][col]], x - screenX, y, scale )
79. endif
80. repeat
81. repeat
82.
102. for i = 0 to len( items ) loop
103. if items[i].state != inactive then
104. x = items[i].x * tSize
105. y = ( items[i].y + levelOffset ) * tSize
106. if playerX + pSize.x > x and playerX < x + tSize and
107. playerY + pSize.y > y and playerY < y + tSize and
108. items[i].state == active
109. then
110. playNote( 0, 3, 1046.50, 1, 20, 0.5 )
111. playNote( 1, 3, 1396.71, 1, 10, 0.5 )
112. playerCoins += 1
113. items[i].state = collect
114. endif
115. if items[i].state == collect then
116. items[i].y -= 0.15
117. if items[i].y < -1 then
118. items[i].state = inactive
119. endif
120. endif
121. drawSheet( tilesheet, itemAnim[items[i].type].start, x - screenX, y, scale )
122. endif
123. repeat
124.
125. if c.a and jumpTimer < 12 then
126. jumpTimer += 1
127. velocity -= 8 / jumpTimer
128. state = jump
129. endif
130.
131. if oldA and !c.a then
132. jumpTimer = 12
133. endif
134.
135. oldA = c.a
136.
137. velocity += gravity
138.
139. if !collision( playerX + pSize.x / 2, playerY + pSize.y + velocity ) then
140. playerY += velocity
141. else
142. playerY = int( ( playerY + velocity + pSize.y ) / tSize ) * tSize - pSize.y
143. velocity = 0
144. jumpTimer = 0
145. state = idle
146. endif
147.
148. if c.right and !collision( playerX + pSize.x / 2 + moveSpeed, playerY + pSize.y - 1 ) then
149. playerX += moveSpeed
150. if state != jump then
151. state = walk
152. endif
153. endif
154.
155. if c.left and !collision( playerX + pSize.x / 2 - moveSpeed, playerY + pSize.y - 1 ) then
156. playerX -= moveSpeed
157. if state != jump then
158. state = walk
159. endif
160. endif
161.
162. animationStart = anim[state].start
163.
164. if animationFrame >= anim[state].length then
165. animationFrame = 0
166. endif
167.
168. drawSheet( chrSheet, animationStart + animationFrame, playerX - screenX, playerY, scale )
169.
170. animationFrame += 0.2
171.
172. update()
173. repeat
174.
175. function collision( x, y )
176. tileX = int( x / tSize )
177. tileY = int( y / tSize ) - levelOffset
178.
179. result = true
180.
181. if tileY < 0 or tileY >= len( level ) or tileX < 0 or tileX >= len( level[0] ) then
182. result = false
183. else
184. if level[tileY][tileX] < 0 then
185. result = false
186. endif
187. endif
188. return result
Functions and Keywords used in this tutorial
clear(), controls(), drawImage(), drawSheet(), drawText(), else, endIf, for, function, gHeight(), gWidth(), if, int(), len(), loadImage(), loop, playNote(), repeat, return, tileSize(), then, to, update()